package net.dgg.rz.common.page;

import net.dgg.tmd.foundation.platform.common.util.ReflectUtil;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;

/**
 * 分页插件
 * 生产环境使用分页插件请注意：
 *      查询语句中请必须有id唯一字段
 *      这样解决返回集中含有集合的情况，出现不准确的总条数
 * @author tanchaowen
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PageInterceptor implements Interceptor {

	/**
	 * 以该词结尾的为需要分页的对象
	 */
	private String endWord = "WithPage";

	/**
	 * 起始条数参数名
	 */
	private final String pageNo = "start";

	/**
	 * 页面大小参数名
	 */
	private final String pageSize = "length";


	private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();

	private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();

	@Override
	public Object intercept(Invocation invocation) throws Throwable {

		StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
		MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
		// 分离代理对象链(由于目标类可能被多个拦截器拦截，从而形成多次代理，通过下面的两次循环可以分离出最原始的的目标类)
		while (metaStatementHandler.hasGetter("h")) {
			Object object = metaStatementHandler.getValue("h");
			metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
		}
		// 分离最后一个代理对象的目标类
		while (metaStatementHandler.hasGetter("target")) {
			Object object = metaStatementHandler.getValue("target");
			metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
		}

		//获取selectid
		MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
		String selectId = mappedStatement.getId();
		int count = 0;
		// 如果以指定词结尾则处理
		if (selectId.endsWith(this.endWord)) {
			BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
			//获取参数
			Map params = (Map) (boundSql.getParameterObject());

			count = this.getTotal(invocation, metaStatementHandler, boundSql);
			params.put("count", count);

			return this.getLimitedData(invocation, metaStatementHandler, boundSql, params);

		}
		Object result = invocation.proceed();
		return result;
	}

	@Override
	public Object plugin(Object target) {
		if (target instanceof StatementHandler) {
			return Plugin.wrap(target, this);
		} else {
			return target;
		}
	}

	@Override
	public void setProperties(Properties p) {

	}


	/**
	 * 统计查询条数SQL
	 *
	 * @param sql
	 * @return
	 */
	private String concatCountSql(String sql) {
		StringBuffer sb = new StringBuffer("select count(DISTINCT _pageing.id) as total from (");
		sb.append(sql);
		sb.append(") as _pageing");

		return sb.toString();
	}

	/**
	 * 改造sql进行分页
	 *
	 * @param sql
	 * @param param
	 * @return
	 */
	private String concatPageSql(String sql, Map param) {
		StringBuffer sb = new StringBuffer();
		int pageNo =Integer.valueOf(param.get(this.pageNo).toString());
		int pageSize = Integer.valueOf(param.get(this.pageSize).toString());
		if(sql.indexOf(") customer_list")>0){
            String[] sqlArray = sql.split("\\) customer_list");
            sb.append(sqlArray[0]);
            sb.append(" limit ")
                    .append(pageNo).append(" , ").append(pageSize);
            sb.append(" ) customer_list ");
            sb.append(sqlArray[1]);
        }else{
            sb.append(sql);
            sb.append(" limit ")
                    .append(pageNo).append(" , ").append(pageSize);
        }
		return sb.toString();
	}

	private int getTotal(Invocation ivt, MetaObject metaStatementHandler, BoundSql boundSql) throws SQLException, NoSuchFieldException, IllegalAccessException {
		//获取当前的mappedStatement
		MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
		//配置对象
		Configuration cfg = mappedStatement.getConfiguration();
		//当前需要执行的sql
		String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
		//改写sql
		String countSql = this.concatCountSql(sql);
		//获取拦截方法参数，我们知道是Connection对象
		Connection connection = (Connection) ivt.getArgs()[0];
		PreparedStatement ps = null;
		int total = 0;
		try {

			//预编译统计总数sql
			ps = connection.prepareStatement(countSql);
			//构建统计总数BoundSql
			BoundSql countBoundSql = new BoundSql(cfg, countSql, boundSql.getParameterMappings(), boundSql.getParameterObject());

			//将原来的metaParameters复制到新的countBoundSql中
			Field metaParamsField = ReflectUtil.getFieldByFieldName(countBoundSql, "metaParameters");
			if (metaParamsField != null) {
				MetaObject mo = (MetaObject) ReflectUtil.getValueByFieldName(boundSql, "metaParameters");
				ReflectUtil.setValueByFieldName(countBoundSql, "metaParameters", mo);
			}

			//将原来的additionalParameters复制到新的countBoundSql中
			Field additionalParametersField = ReflectUtil.getFieldByFieldName(countBoundSql, "additionalParameters");
			if (additionalParametersField != null) {
				Object mo = ReflectUtil.getValueByFieldName(boundSql, "additionalParameters");
				ReflectUtil.setValueByFieldName(countBoundSql, "additionalParameters", mo);
			}

			//构建Mybatis的ParameterHandler用来设置总数sql参数
			ParameterHandler handler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBoundSql);
			//设置总数sql参数
			handler.setParameters(ps);
			//执行查询
			ResultSet rs = ps.executeQuery();
			while (rs.next()) {
				total = rs.getInt(1);
			}

		} finally {
		}

		return total;
	}


	private Object getLimitedData(Invocation invocation, MetaObject metaStatementHandler, BoundSql boundSql, Map param) throws InvocationTargetException, IllegalAccessException {
		//获取当前需要执行的sql
		String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
		//修改sql，这里使用的是mysql，如果是其他数据库则需要修改
		if (null != param.get(this.pageNo) && null != param.get(this.pageSize)) {
			sql = this.concatPageSql(sql, param);
	     }
		//修改当前需要执行的sql
		metaStatementHandler.setValue("delegate.boundSql.sql", sql);
		//相当于调用StatementeHandler的prepare方法，预编译了当前sql，并设置原有的参数，但是少了两个分页参数，它返回的是一个PreparedStatement对象
		PreparedStatement ps = (PreparedStatement) invocation.proceed();
		return ps;

	}

}
